﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Exocortex.DSP;

namespace FMOD
{
    public class SnippetChannel
    {
        private readonly List<float[]> _blocks = new List<float[]>();

        private float[] _data;
        private bool _dirty;
        private bool _dirtySpectral;
        private float[] _spectralData;

        public float[] Data
        {
            get
            {
                if (_dirty)
                {
                    // optimization
                    _data = _blocks.Count == 1 ? _blocks[0] : _blocks.SelectMany(b => b).ToArray();
                    _dirty = false;
                }

                return _data;
            }
        }

        public float[] SpectralData
        {
            get
            {
                if (_dirtySpectral)
                {
                    int length = Data.Length;
                    if (length%2 != 0)
                        length++;

                    _spectralData = new float[length];
                    Array.Copy(Data, _spectralData, Data.Length);
                    Fourier.FFT(_spectralData, _spectralData.Length/2, FourierDirection.Forward);
                    _dirtySpectral = false;
                }

                return _spectralData;
            }
        }

        public float MaxValue
        {
            get { return Data.Max(); }
        }

        public float MinValue
        {
            get { return Data.Min(); }
        }

        public float Average
        {
            get { return Data.Average(); }
        }

        public void AddData(float[] data)
        {
            _blocks.Add(data);
            _dirty = _dirtySpectral = true;
        }
    }

    public class Snippet
    {
        private const float Int16MaxValue = Int16.MaxValue;
        private readonly List<byte[]> _blocks = new List<byte[]>();

        public Snippet(TimeSpan start, int bits, float sampleRate, int channelCount)
        {
            if (bits != 16 && bits != 32)
                throw new NotSupportedException("Only 16 or 32 bit channels supported");

            Start = start;
            Bits = bits;
            SampleRate = sampleRate;
            ChannelCount = channelCount;

            Clear();
        }

        public TimeSpan Start { get; private set; }

        public TimeSpan Length
        {
            get
            {
                if (Channels == null || Channels[0] == null || Channels[0].Data == null)
                    throw new InvalidOperationException("Snippet has not been processed");

                return ToTime(Channels[0].Data.Length);
            }
        }

        public List<SnippetChannel> Channels { get; private set; }
        public int MaxTargetSamplesPerChannel { get; private set; }
        public int Bits { get; private set; }
        public float SampleRate { get; private set; }
        public double TotalSourceSamples { get; private set; }
        public int ChannelCount { get; private set; }

        public void Clear()
        {
            Channels = new List<SnippetChannel>();
            for (int i = 0; i < ChannelCount; i++)
                Channels.Add(new SnippetChannel());
        }

        protected static void Convert16(byte[] inData, float[] outData, int decimation, int channelCount)
        {
            int sampleDecimation = decimation*2;
            for (int idxOut = 0, idxIn = 0; idxIn < inData.Length; idxOut++, idxIn += sampleDecimation)
            {
                for (int channel = 0; channel < channelCount; channel++)
                {
                    float value = BitConverter.ToInt16(inData, idxIn + channel*2);
                    outData[idxOut + channel] = value/Int16MaxValue;
                }
            }
        }

        protected static void Convert32Float(byte[] inData, float[] outData, int decimation, int channelCount)
        {
            int sampleDecimation = decimation*4;
            for (int idxOut = 0, idxIn = 0; idxIn < inData.Length; idxOut++, idxIn += sampleDecimation)
            {
                for (int channel = 0; channel < channelCount; channel++)
                {
                    float value = BitConverter.ToSingle(inData, idxIn + channel*4);
                    outData[idxOut + channel] = value/0.01f;
                }
            }
        }

        public TimeSpan ToTime(double samples)
        {
            return TimeSpan.FromMilliseconds(samples*1000*(Bits/8)/(SampleRate));
        }

        public void AddPcmBlock(byte[] block)
        {
            _blocks.Add(block.ToArray());
        }

        public void Process()
        {
            Process(AudioTrack.DefaultDecimationInterval);
        }

        public void Process(int decimation)
        {
            foreach (var block in _blocks)
                ProcessPcmBlock(block, decimation);
            _blocks.Clear();
        }

        private void ProcessPcmBlock(byte[] block, int decimation)
        {
            if (block.Length == 0)
                return;

            int bytes = Bits/8;
            int decimatedLength = (int) Math.Ceiling((double) block.Length/(decimation*bytes)) + 1;

            var sampleBlock = new float[decimatedLength];

            switch (Bits)
            {
                case 16:
                    Convert16(block, sampleBlock, decimation, ChannelCount);
                    break;
                case 32:
                    Convert32Float(block, sampleBlock, decimation, ChannelCount);
                    break;
                default:
                    throw new InvalidDataException("Only 16- and 32-bit supported");
            }

            if (ChannelCount == 1)
            {
                Channels[0].AddData(sampleBlock);
            }
            else
            {
                /* Deinterleave */
                int samplesPerChannel = decimatedLength/ChannelCount;

                for (int channelNo = 0; channelNo < ChannelCount; channelNo++)
                {
                    var channelBlock = new float[samplesPerChannel];

                    for (int i = 0; i < samplesPerChannel; i++)
                    {
                        channelBlock[i] = sampleBlock[channelNo + i*ChannelCount];
                    }

                    Channels[channelNo].AddData(channelBlock);
                }
            }
        }
    }
}